<!--
 * @Author: 朱相阳
 * @Date: 2025-07-09 16:24:00
 * @Description: 动态表单
-->
<script setup lang="ts">
import type { IFormItem } from '@/components/NFormBuilder/types'
import type { FormInst, FormProps, FormRules, GridProps } from 'naive-ui'
import { getBindingKey, getFormItemComponent } from './config'
import { computed, h } from 'vue'
import { get, set } from 'lodash-es'
defineOptions({
  name: 'NFormBuilder',
})

const emit = defineEmits<{
  'update:modelValue': [value: Record<string, unknown>]
}>()

const baseFieldReg = /^(type|label|props|on|span|key|hidden|required|rules|col|formProps)$/

export interface FormBuldProps {
  formItems: IFormItem[] | Ref<IFormItem[]>
  rules?: FormRules | Ref<FormRules>
  formConfig?: FormProps
  gridConfig?: GridProps
}
// 定义组件属性并设置默认值
const props = withDefaults(defineProps<FormBuldProps>(), {
  formItems: () => [], // 默认表单项为空数组
  formConfig: () => ({}), // 默认表单配置为空对象
  gridConfig: () => ({}), // 默认栅格配置为空对象
})

const gridProps = computed(() => {
  return {
    xGap: 12,
    ...props.gridConfig,
  } as GridProps
})

// 表单实例引用
const formRef = ref<FormInst>()

// 定义表单数据模型
const formData = defineModel<Record<string, unknown>>({
  default: () => ({}), // 默认值为空对象
})

// 计算表单项，过滤掉隐藏的项
const formItemsComputed = computed(() =>
  props.formItems.filter((item) =>
    typeof item.hidden === 'function' ? !item.hidden() : !item.hidden,
  ),
)
/**
 * 获取表单项属性
 */
function getFormItemProps(formItem: IFormItem) {
  const { formProps = {} } = formItem
  const props = formProps as Record<string, unknown>
  if (!props.label && formItem.label) {
    props.label = formItem.label
  }
  return {
    ...props, // 其他属性
  }
}
/**
 * 获取布局配置
 */
function getGridItemProps(formItem: IFormItem) {
  const { giProps = {} } = formItem
  if (!giProps.span) {
    giProps.span = formItem?.span || 24
  }
  return {
    ...giProps, // 宫格布局配置
  }
}

function getFormItemPath(field: string | string[]): string {
  return Array.isArray(field) ? field[0] : field
}

const selectType = new Set(['select', 'date', 'time', 'treeSelect'])
/**
 * 更新表单字段值
 */
function updateFieldValue(field: string | string[], value: unknown) {
  if (Array.isArray(field)) {
    field.forEach((fieldPath, index) => {
      set(formData.value, fieldPath, value?.[index] ?? null)
    })
  } else {
    set(formData.value, field, value)
  }
  emit('update:modelValue', formData.value)
}

const ComponentItem = {
  props: ['item'],
  setup(_props: { item: IFormItem }) {
    return () => {
      const item = _props.item
      const props = Object.keys(item).reduce<Record<string, unknown>>(
        (prev, key) => {
          if (!baseFieldReg.test(key)) {
            prev[key] = item[key]
          }
          return prev
        },
        { ...item.props, formData: formData.value },
      )

      if (!('placeholder' in props)) {
        const { type } = item
        const text = selectType.has(type as string) ? '请选择' : '请输入'
        props.placeholder = text + item.label
      }
      if (item.type === 'number') {
        if (!('showButton' in props)) {
          props.showButton = false
        }
      }
      if (
        ['select', 'number', 'datePicker', 'datePickerFormat', 'tree-select'].includes(item.type)
      ) {
        if (!('class' in props)) {
          props.class = 'w-full'
        } else {
          props.class = `${props.class} w-full`
        }
      }
      const bindingKey = getBindingKey(item.type as string) || 'modelValue'
      const updateKey =
        bindingKey === 'modelValue' ? 'onUpdate:modelValue' : `onUpdate:${bindingKey}`

      // 获取绑定值
      const getBindingValue = () => {
        if (Array.isArray(item.field)) {
          const values = item.field.map((el) => get(formData.value, el))
          return values.some((v) => v != null) ? values : null
        } else {
          return get(formData.value, item.field)
        }
      }
      const tag = getFormItemComponent(item.type) // NInput
      return h(
        tag, // NInput
        {
          ...props,
          // v-model 会生成这两个参数 modelValue 和 onUpdate:modelValue
          [bindingKey]: getBindingValue(),
          [updateKey]: (val) => updateFieldValue(item.field, val), // 更新 modelValue v-model
        },
        item.slots, // 插槽内容
      )
    }
  },
}

/**
 * 验证表单
 */
function validate() {
  return formRef.value?.validate()
}
defineExpose({
  validate,
  restoreValidation: () => {
    formRef.value?.restoreValidation()
  },
})
</script>

<template>
  <n-form ref="formRef" :model="formData" :rules="rules" v-bind="formConfig">
    <template v-if="formConfig.inline">
      <n-form-item
        v-for="item in formItemsComputed"
        :key="getFormItemPath(item.field)"
        :path="getFormItemPath(item.field)"
        v-bind="getFormItemProps(item)"
      >
        <slot :name="item.field">
          <ComponentItem :item="item"></ComponentItem>
        </slot>
      </n-form-item>
      <slot name="btns">
        <n-form-item> </n-form-item>
      </slot>
    </template>
    <template v-else>
      <n-grid v-bind="gridProps">
        <n-gi
          v-for="item in formItemsComputed"
          :key="getFormItemPath(item.field)"
          v-bind="getGridItemProps(item)"
        >
          <n-form-item :path="getFormItemPath(item.field)" v-bind="getFormItemProps(item)">
            <slot :name="item.field">
              <ComponentItem :item="item"></ComponentItem>
            </slot>
          </n-form-item>
        </n-gi>
      </n-grid>
    </template>
  </n-form>
</template>

<style scoped></style>
